<

Mockito を使用して依存関係をモックする

単体テストは、ライブからデータをフェッチするクラスに依存する場合があります。 Web サービスまたはデータベース。これはいくつかの理由から不便です。

  • ライブ サービスまたはデータベースを呼び出すと、テストの実行が遅くなります。
  • Web サービスまたはデータベースが返されると、合格したテストでも失敗し始める可能性があります 予想外の結果。これは「不安定なテスト」として知られています。
  • 考えられるすべての成功シナリオと失敗シナリオをテストすることは困難です ライブ Web サービスまたはデータベースを使用する。

したがって、ライブ Web サービスやデータベースに依存するのではなく、 これらの依存関係を「モック」することができます。モックを使用するとライブをエミュレートできます Web サービスまたはデータベースに応じて特定の結果を返します。 状況について。

一般に、代替を作成することで依存関係を模擬できます。 クラスの実装。これらの代替実装を次のように記述します。 手を使ったり、使ったり、Mockito パッケージショートカットとして。

このレシピでは、 次の手順を使用して Mockito パッケージを作成します。

  1. パッケージの依存関係を追加します。
  2. テストする関数を作成します。
  3. モックを含むテストファイルを作成するhttp.Client
  4. 条件ごとにテストを作成します。
  5. テストを実行します。

詳細については、「Mockito パッケージドキュメンテーション。

1. パッケージの依存関係を追加します。

を使用するには、mockitoパッケージに追加します。pubspec.yamlファイルと一緒にflutter_testの依存関係dev_dependenciesセクション。

この例では、httpパッケージ、 したがって、その依存関係をdependenciesセクション。

mockito: 5.0.0コード生成により、Dart の null 安全性がサポートされます。 必要なコード生成を実行するには、build_runner依存 の中にdev_dependenciesセクション。

依存関係を追加するには、次を実行します。flutter pub add:

$ flutter pub add http dev:mockito dev:build_runner

2. テストする関数を作成する

この例では、単体テストを行います。fetchAlbumからの関数インターネットからデータを取得するレシピ。 この関数をテストするには、次の 2 つの変更を加えます。

  1. 提供するhttp.Client機能に。これにより、 正しいhttp.Client状況に応じて。 Flutter およびサーバー側プロジェクトの場合は、http.IOClient。 ブラウザ アプリの場合は、http.BrowserClient。 テストの場合はモックを提供しますhttp.Client
  2. 提供されているものを使用しますclientインターネットからデータを取得するには、 静的なものではなくhttp.get()モックするのが難しいメソッドです。

関数は次のようになります。

Future<Album> fetchAlbum(http.Client client) async {
  final response = await client
      .get(Uri.parse('https://jsonplaceholder.typicode.com/albums/1'));

  if (response.statusCode == 200) {
    // If the server did return a 200 OK response,
    // then parse the JSON.
    return Album.fromJson(jsonDecode(response.body));
  } else {
    // If the server did not return a 200 OK response,
    // then throw an exception.
    throw Exception('Failed to load album');
  }
}

アプリのコードでは、http.ClientfetchAlbum方法 直接fetchAlbum(http.Client())http.Client()デフォルトを作成しますhttp.Client

3. モックを含むテスト ファイルを作成するhttp.Client

次にテストファイルを作成します。

のアドバイスに従って、単体テストの概要レシピ、 というファイルを作成しますfetch_album_test.dart根元にあるtestフォルダ。

注釈を追加する@GenerateMocks([http.Client])メインへ を生成する関数MockClientとのクラスmockito

生成されたMockClientクラスはhttp.Clientクラス。 これにより、MockClientfetchAlbum関数、 各テストで異なる http 応答を返します。

生成されたモックは次の場所にあります。fetch_album_test.mocks.dart。 使用するにはこのファイルをインポートしてください。

import 'package:http/http.dart' as http;
import 'package:mocking/main.dart';
import 'package:mockito/annotations.dart';

// Generate a MockClient using the Mockito package.
// Create new instances of this class in each test.
@GenerateMocks([http.Client])
void main() {
}

次に、次のコマンドを実行してモックを生成します。

$ flutter pub run build_runner build

4. 各条件のテストを作成する

fetchAlbum()関数は次の 2 つのいずれかを実行します。

  1. を返しますAlbumhttp呼び出しが成功した場合
  2. を投げますExceptionhttp 呼び出しが失敗した場合

したがって、これら 2 つの条件をテストする必要があります。 使用MockClient「OK」応答を返すクラス 成功したテストの場合はエラー応答が返され、失敗したテストの場合はエラー応答が返されます。 これらの条件を次を使用してテストします。when()が提供する機能 モキト:

import 'package:flutter_test/flutter_test.dart';
import 'package:http/http.dart' as http;
import 'package:mocking/main.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';

import 'fetch_album_test.mocks.dart';

// Generate a MockClient using the Mockito package.
// Create new instances of this class in each test.
@GenerateMocks([http.Client])
void main() {
  group('fetchAlbum', () {
    test('returns an Album if the http call completes successfully', () async {
      final client = MockClient();

      // Use Mockito to return a successful response when it calls the
      // provided http.Client.
      when(client
              .get(Uri.parse('https://jsonplaceholder.typicode.com/albums/1')))
          .thenAnswer((_) async =>
              http.Response('{"userId": 1, "id": 2, "title": "mock"}', 200));

      expect(await fetchAlbum(client), isA<Album>());
    });

    test('throws an exception if the http call completes with an error', () {
      final client = MockClient();

      // Use Mockito to return an unsuccessful response when it calls the
      // provided http.Client.
      when(client
              .get(Uri.parse('https://jsonplaceholder.typicode.com/albums/1')))
          .thenAnswer((_) async => http.Response('Not Found', 404));

      expect(fetchAlbum(client), throwsException);
    });
  });
}

5. テストを実行する

これで、fetchAlbum()テストを実施した機能、 テストを実行します。

$ flutter test test/fetch_album_test.dart

次の手順に従って、お気に入りのエディタ内でテストを実行することもできます。 の指示単体テストの概要レシピ。

完全な例

lib/main.dart
import 'dart:async';
import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;

Future<Album> fetchAlbum(http.Client client) async {
  final response = await client
      .get(Uri.parse('https://jsonplaceholder.typicode.com/albums/1'));

  if (response.statusCode == 200) {
    // If the server did return a 200 OK response,
    // then parse the JSON.
    return Album.fromJson(jsonDecode(response.body));
  } else {
    // If the server did not return a 200 OK response,
    // then throw an exception.
    throw Exception('Failed to load album');
  }
}

class Album {
  final int userId;
  final int id;
  final String title;

  const Album({required this.userId, required this.id, required this.title});

  factory Album.fromJson(Map<String, dynamic> json) {
    return Album(
      userId: json['userId'],
      id: json['id'],
      title: json['title'],
    );
  }
}

void main() => runApp(const MyApp());

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  late final Future<Album> futureAlbum;

  @override
  void initState() {
    super.initState();
    futureAlbum = fetchAlbum(http.Client());
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Fetch Data Example',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Fetch Data Example'),
        ),
        body: Center(
          child: FutureBuilder<Album>(
            future: futureAlbum,
            builder: (context, snapshot) {
              if (snapshot.hasData) {
                return Text(snapshot.data!.title);
              } else if (snapshot.hasError) {
                return Text('${snapshot.error}');
              }

              // By default, show a loading spinner.
              return const CircularProgressIndicator();
            },
          ),
        ),
      ),
    );
  }
}
テスト/fetch_album_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'package:http/http.dart' as http;
import 'package:mocking/main.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';

import 'fetch_album_test.mocks.dart';

// Generate a MockClient using the Mockito package.
// Create new instances of this class in each test.
@GenerateMocks([http.Client])
void main() {
  group('fetchAlbum', () {
    test('returns an Album if the http call completes successfully', () async {
      final client = MockClient();

      // Use Mockito to return a successful response when it calls the
      // provided http.Client.
      when(client
              .get(Uri.parse('https://jsonplaceholder.typicode.com/albums/1')))
          .thenAnswer((_) async =>
              http.Response('{"userId": 1, "id": 2, "title": "mock"}', 200));

      expect(await fetchAlbum(client), isA<Album>());
    });

    test('throws an exception if the http call completes with an error', () {
      final client = MockClient();

      // Use Mockito to return an unsuccessful response when it calls the
      // provided http.Client.
      when(client
              .get(Uri.parse('https://jsonplaceholder.typicode.com/albums/1')))
          .thenAnswer((_) async => http.Response('Not Found', 404));

      expect(fetchAlbum(client), throwsException);
    });
  });
}

まとめ

この例では、Mockito を使用して関数またはクラスをテストする方法を学習しました。 Web サービスまたはデータベースに依存するもの。これはほんの短い紹介です Mockito ライブラリとモッキングの概念。詳細については、 によって提供されるドキュメントを参照してください。Mockito パッケージ。